package eval

import (
	"showen/lex"
	"showen/obj"
	"showen/parse"
	"testing"
)

func TestLetRecIntegration(t *testing.T) {
	env := obj.NewEnvironment()
	RegisterBuiltinProcedures(env)

	tests := []struct {
		name     string
		input    string
		expected string
	}{
		{
			name:     "factorial function",
			input:    "(letrec ((fact (lambda (n) (if (= n 0) 1 (* n (fact (- n 1))))))) (fact 5))",
			expected: "120",
		},
		{
			name:     "mutual recursion - even?",
			input:    "(letrec ((even? (lambda (n) (if (= n 0) #t (odd? (- n 1))))) (odd? (lambda (n) (if (= n 0) #f (even? (- n 1)))))) (even? 4))",
			expected: "#t",
		},
		{
			name:     "mutual recursion - odd?",
			input:    "(letrec ((even? (lambda (n) (if (= n 0) #t (odd? (- n 1))))) (odd? (lambda (n) (if (= n 0) #f (even? (- n 1)))))) (odd? 5))",
			expected: "#t",
		},
		{
			name:     "simple value binding",
			input:    "(letrec ((x 10)) x)",
			expected: "10",
		},
		{
			name:     "forward reference",
			input:    "(letrec ((f (lambda () x)) (x 10)) (f))",
			expected: "10",
		},
		{
			name:     "multiple expressions in body",
			input:    "(letrec ((x 5) (y 10)) (+ x y))",
			expected: "15",
		},
		{
			name:     "nested letrec",
			input:    "(letrec ((outer (lambda (x) (letrec ((inner (lambda (y) (+ x y)))) (inner 2))))) (outer 3))",
			expected: "5",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Lex
			lexer := lex.NewLexer(tt.input)
			tokens := lexer.ReadTokens()

			// Parse
			parser := parse.NewParser(tokens)
			program, err := parser.ParseProgram()
			if err != nil {
				t.Fatalf("Parse error: %v", err)
			}

			// Eval
			var result obj.Object
			for _, expr := range program {
				result, err = EvalExpression(expr, env)
				if err != nil {
					t.Fatalf("Eval error: %v", err)
				}
			}

			// Check result
			if result.Inspect() != tt.expected {
				t.Errorf("Expected %s, got %s", tt.expected, result.Inspect())
			}
		})
	}
}

func TestLetRecErrorHandling(t *testing.T) {
	env := obj.NewEnvironment()
	RegisterBuiltinProcedures(env)

	tests := []struct {
		name      string
		input     string
		expectErr bool
	}{
		{
			name:      "letrec with wrong number of arguments",
			input:     "(letrec)",
			expectErr: true,
		},
		{
			name:      "letrec with only bindings",
			input:     "(letrec ((x 10)))",
			expectErr: true,
		},
		{
			name:      "letrec with non-list bindings",
			input:     "(letrec x 10)",
			expectErr: true,
		},
		{
			name:      "letrec with malformed binding",
			input:     "(letrec ((x)) x)",
			expectErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Lex
			lexer := lex.NewLexer(tt.input)
			tokens := lexer.ReadTokens()

			// Parse
			parser := parse.NewParser(tokens)
			program, err := parser.ParseProgram()
			if err != nil {
				if !tt.expectErr {
					t.Fatalf("Unexpected parse error: %v", err)
				}
				return
			}

			// Eval
			for _, expr := range program {
				_, err = EvalExpression(expr, env)
				if err != nil {
					if !tt.expectErr {
						t.Fatalf("Unexpected eval error: %v", err)
					}
					return
				}
			}

			if tt.expectErr {
				t.Errorf("Expected error, but no error occurred")
			}
		})
	}
}